package com.ziroom.crm.mq.core.spi;

import com.ziroom.crm.mq.core.common.CommonConst;
import com.ziroom.crm.mq.core.config.MqConfig;
import com.ziroom.crm.mq.core.exception.MqDealException;
import com.ziroom.crm.mq.core.logging.Log;
import com.ziroom.crm.mq.core.logging.LogFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * <p></p>
 * <p>
 * <PRE>
 * <BR>    修改记录
 * <BR>-----------------------------------------------
 * <BR>    修改日期         修改人          修改内容
 * </PRE>
 *
 * @author WeiShaoying
 * @version 1.0
 * @date Created in 2020年08月28日 17:42
 * @since 1.0
 */
@Slf4j
@SuppressWarnings("ALL")
public class SpiLoader {

    private static final Map<String, ServiceLoader> SERVICE_LOADER_MAP = new ConcurrentHashMap<>();

    private static final Map<String, Set<String>> EXCLUDE_SPI_MAP = new ConcurrentHashMap<>();

    /**
     * Load the SPI instance with highest priority.
     * <p>
     * Note: each call return same instances.
     *
     * @param clazz class of the SPI
     * @param <T>   SPI type
     * @return the SPI instance with highest priority if exists, or else false
     * @since 1.6.0
     */
    public static <T> T loadHighestPriorityInstance(Class<T> clazz) {
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            SpiOrderWrapper<T> w = null;
            for (T spi : serviceLoader) {
                if (hitSpiExcludeConfig(spi)) {
                    continue;
                }
                int order = SpiOrderResolver.resolveOrder(spi);
                log.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName(), order);
                if (w == null || order < w.order) {
                    w = new SpiOrderWrapper<>(order, spi);
                }
            }
            return w == null ? null : w.spi;
        } catch (Throwable t) {
            log.error("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t);
            t.printStackTrace();
            return null;
        }
    }

    /**
     * Load the sorted SPI instance list for provided SPI interface.
     * <p>
     * Note: each call return same instances.
     *
     * @param clazz class of the SPI
     * @param <T>   SPI type
     * @return sorted SPI instance list
     * @since 1.6.0
     */
    public static <T> List<T> loadInstanceListSorted(Class<T> clazz) {
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            List<SpiOrderWrapper<T>> orderWrappers = new ArrayList<>();
            for (T spi : serviceLoader) {
                if (hitSpiExcludeConfig(spi)) {
                    continue;
                }
                int order = SpiOrderResolver.resolveOrder(spi);
                // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here.
                SpiOrderResolver.insertSorted(orderWrappers, spi, order);
                log.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName(), order);
            }
            List<T> list = new ArrayList<>(orderWrappers.size());
            for (SpiOrderWrapper<T> orderWrapper : orderWrappers) {
                list.add(orderWrapper.spi);
            }
            log.info("SPI loadInstanceListSorted 加载{}", clazz.getSimpleName());
            //list.forEach(x -> System.out.println("==" + x.getClass().getCanonicalName()));
            return list;
        } catch (Throwable t) {
            log.error("[SpiLoader] ERROR: loadInstanceListSorted failed", t);
            t.printStackTrace();
            return new ArrayList<>();
        }
    }

    /**
     * Load the sorted and prototype SPI instance list for provided SPI interface.
     * <p>
     * Note: each call return different instances, i.e. prototype instance, not singleton instance.
     *
     * @param clazz class of the SPI
     * @param <T>   SPI type
     * @return sorted and different SPI instance list
     */
    public static <T> List<T> loadPrototypeInstanceListSorted(Class<T> clazz) {
        try {
            // Not use SERVICE_LOADER_MAP, to make sure the instances loaded are different.
            ServiceLoader<T> serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);

            List<SpiOrderWrapper<T>> orderWrappers = new ArrayList<>();
            for (T spi : serviceLoader) {
                if (hitSpiExcludeConfig(spi)) {
                    continue;
                }
                int order = SpiOrderResolver.resolveOrder(spi);
                // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here.
                SpiOrderResolver.insertSorted(orderWrappers, spi, order);
                log.debug("[SpiLoader] Found " + clazz.getSimpleName() + " SPI: " + spi.getClass().getCanonicalName() + " with order " + order);
            }
            List<T> list = new ArrayList<>(orderWrappers.size());
            for (SpiOrderWrapper<T> orderWrapper : orderWrappers) {
                list.add(orderWrapper.spi);
            }
            log.info("SPI loadPrototypeInstanceListSorted 加载{}", clazz.getSimpleName());
            return list;
        } catch (Throwable t) {
            log.error("[SpiLoader] ERROR: loadPrototypeInstanceListSorted failed", t);
            t.printStackTrace();
            return new ArrayList<>();
        }
    }

    /**
     * 将带有{@linkplain SpiAlias 别名}的spi class 加载成map, key=alias , value = instance
     * 遇到重复时，会抛出异常
     * 加载类没有别名标注时，抛弃不返回
     *
     * @param
     * @return
     */
    public static <T> Map<String, Map<String, T>> loadPrototypeInstanceMapWithAlias(Class<T> clazz) {
        try {
            ServiceLoader<T> serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);

            Map<String, Map<String, T>> groupMap = new HashMap<>(4);
            for (T spi : serviceLoader) {
                if (hitSpiExcludeConfig(spi)) {
                    continue;
                }
                // 别名
                String spiAliasName = getSpiAliasName(spi);
                if (StringUtils.isBlank(spiAliasName)) {
                    continue;
                }
                // 分组名
                String spiAliasGroup = getSpiAliasGroup(spi);
                Map<String, T> aliasMap = groupMap.computeIfAbsent(spiAliasGroup, (a) -> new HashMap<>(4));
                if (aliasMap.containsKey(spiAliasName)) {
                    throw new MqDealException("SpiAlias [" + spiAliasName + "] has existed , please make sure your config at class :" + spi.getClass().getCanonicalName());
                }

                aliasMap.put(spiAliasName, spi);

            }
            return groupMap;
        } catch (Throwable t) {
            log.error("[SpiLoader] ERROR: loadPrototypeInstanceListSorted failed", t);
            t.printStackTrace();
            return new HashMap<>(0);
        }
    }

    public static <T> T loadPrototypeInstanceByAlias(Class<T> clazz, String alias, String group) {
        Map<String, T> aliasMap = loadPrototypeInstanceMapWithAlias(clazz).get(group);
        return Objects.isNull(aliasMap) ? null : aliasMap.get(alias);
    }

    /**
     * 过滤掉配置中要排除的 spi class配置
     *
     * @param loader 加载器
     * @return 是否要被排除
     */
    private static <T> boolean hitSpiExcludeConfig(T loader) {
        String excludeSpiClasses = MqConfig.getConfig(MqConfig.SPI_EXCLUDE_CLASS);
        // 没有配置就不过滤了
        if (StringUtils.isBlank(excludeSpiClasses)) {
            return false;
        }
        Set<String> set = EXCLUDE_SPI_MAP.get(excludeSpiClasses);
        if (Objects.isNull(set)) {
            set = Arrays.stream(excludeSpiClasses.split(CommonConst.SYMBOL_COMMA)).collect(Collectors.toSet());
            EXCLUDE_SPI_MAP.put(excludeSpiClasses, set);
        }

        // 包含则排除
        return set.contains(loader.getClass().getCanonicalName());
    }


    private static class SpiOrderResolver {
        private static <T> void insertSorted(List<SpiOrderWrapper<T>> list, T spi, int order) {
            int idx = 0;
            for (; idx < list.size(); idx++) {
                if (list.get(idx).getOrder() > order) {
                    break;
                }
            }
            list.add(idx, new SpiOrderWrapper<>(order, spi));
        }

        private static <T> int resolveOrder(T spi) {
            if (!spi.getClass().isAnnotationPresent(SpiOrder.class)) {
                // Lowest precedence by default.
                return SpiOrder.LOWEST_PRECEDENCE;
            } else {
                return spi.getClass().getAnnotation(SpiOrder.class).value();
            }
        }
    }

    private static String getSpiAliasName(Object spi) {
        if (spi.getClass().isAnnotationPresent(SpiAlias.class)) {
            return spi.getClass().getAnnotation(SpiAlias.class).value();
        }

        // 如果不想将没有别名的过滤，可以考虑返回class 类名
        return null;
    }

    private static String getSpiAliasGroup(Object spi) {
        if (spi.getClass().isAnnotationPresent(SpiAlias.class)) {
            return spi.getClass().getAnnotation(SpiAlias.class).group();
        }

        return null;
    }

    private static class SpiOrderWrapper<T> {
        private final int order;
        private final T spi;

        SpiOrderWrapper(int order, T spi) {
            this.order = order;
            this.spi = spi;
        }

        int getOrder() {
            return order;
        }

        T getSpi() {
            return spi;
        }
    }
}
